fix(duplication): preserve Elementor Kit postmeta across all URL replacement passes#803
Conversation
…acement passes MUCD_Data::update() previously used `WHERE meta_value = %s` with the full serialized field value to identify rows for UPDATE. With 7+ replacement passes (upload URL, blog URL, prefix, JSON-escaped variants), the second pass would search for the original value that had already been rewritten by pass 1 — finding nothing and silently skipping. On large TEXT columns this also risks engine-level comparison truncation. For the Elementor Kit post (post_id=3, meta_key=_elementor_page_settings), this caused 748 bytes of global settings — brand colors, typography, custom CSS — to be lost on clone. Cloned sites fell back to Elementor's built-in defaults instead of the template's styles. Changes: - MUCD_Data::get_primary_key(): new static method with per-table cache that queries SHOW KEYS to get the PRIMARY column name. - MUCD_Data::update(): use `WHERE pk = %s` when a primary key is available (postmeta → meta_id, options → option_id, posts → ID). Falls back to full-value WHERE for tables without a detectable PK. Skips UPDATE entirely when try_replace() produces no change (avoids unnecessary writes). - MUCD_Data::try_replace(): guard against @unserialize() returning false on malformed data — return the original value instead of re-serializing false (which would produce 'b:0;' and destroy the row). - Elementor_Compat::regenerate_css(): add DB-level fallback for clearing Elementor CSS cache when \Elementor\Plugin is not loaded (the common case in network admin where duplication runs). Tests (22 tests, 85 assertions): - test_update_preserves_elementor_kit_byte_count: integration test that inserts a realistic ~6387-byte Kit payload into wp_postmeta, runs all 7 URL replacement passes via MUCD_Data::update(), then asserts colors (#6EC1E4, #54595F, #7A7A7A, #61CE70), typography (Roboto, Open Sans, Montserrat), and URL rewrites are all intact and byte count changes only by the predictable URL-length delta. - test_get_primary_key_returns_correct_column / _is_cached - test_try_replace_elementor_kit_page_settings - test_try_replace_multiple_passes_preserve_data - test_try_replace_returns_original_on_unserialize_failure - test_try_replace_serialized_false_not_treated_as_error
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe pull request enhances cache invalidation and URL replacement logic across site duplication. The Elementor compatibility layer now implements a DB-based fallback for CSS cache clearing when the Elementor API is unavailable. Data duplication improvements add primary-key detection and refine serialized data handling to preserve data integrity across nested structures. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
ProblemWhen cloning a site that uses Elementor, the SELECT 'origin', LENGTH(meta_value) FROM wp_ORIGIN_postmeta
WHERE post_id=3 AND meta_key='_elementor_page_settings'
UNION ALL
SELECT 'clone', LENGTH(meta_value) FROM wp_CLONE_postmeta
WHERE post_id=3 AND meta_key='_elementor_page_settings';Root causes1. Stale WHERE clause in Fix
Tests22 tests / 85 assertions, all passing.
Files
Verificationvendor/bin/phpunit --no-coverage --filter MUCD_Data_Test
# Expected: OK (22 tests, 85 assertions)Merged via PR #803 to main. aidevops.sh v3.7.1 spent 5m on this as a headless bash routine. |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
|
Performance Test Results Performance test results for 95baf22 are in 🛎️! Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown. URL:
|
|
Hi @superdav42 — URGENT follow-up: PR #803 fixed the Kit URL replacement, but in production at https://kursopro.com (~250+ subsites) we still get broken clones daily. Wrong colors, missing logos, broken menus, "TU LOGO AQUI" placeholders. The postmeta layer for I've been running a 774-line mu-plugin patch in production since 2026-04-13 with zero failures. Filed full bug report + production-tested code + step-by-step integration instructions here: Once you integrate this into Multisite Ultimate natively, I can retire 2 mu-plugins from our stack. Happy to test any branch. Thanks! |
Problem
When cloning a site that uses Elementor, the
_elementor_page_settingspostmeta for post_id=3 (the Elementor Global Kit) loses ~748 bytes. Origin template has 6,387 bytes; clone receives 5,639. Cloned sites render with Elementor's built-in default styles instead of the template's brand colours and typography.Reproduce:
Root causes
1. Stale WHERE clause in
MUCD_Data::update()The method built
UPDATE table SET field = %s WHERE field = %susing the full serialized meta_value in the WHERE. With 7+ replacement passes (upload URL, blog URL, DB prefix, plus JSON-escaped variants for each), pass 1 rewrites the row. Pass 2 then searches for the original value — which no longer exists — and silently skips. The row is left with only the first pass's changes. On largeTEXTcolumns, full-value equality comparison can also trigger engine-level truncation.2. Silent data destruction on
@unserialize()failuretry_replace()called@unserialize()and never checked forfalse. If unserialize failed (malformed or partially-corrupted input), the code continued with$row[$field] = false, thenserialize(false)→'b:0;'— destroying the entire value.Fix
inc/duplication/data.phpMUCD_Data::get_primary_key($table): new static method with per-table static cache. QueriesSHOW KEYS ... WHERE Key_name = 'PRIMARY'to get the PK column (e.g.meta_idfor postmeta,IDfor posts,option_idfor options).MUCD_Data::update(): usesWHERE pk = %swhen a primary key is detectable; falls back to full-value WHERE for tables without a detectable PK. Also skips theUPDATEcall entirely whentry_replace()produces no change.MUCD_Data::try_replace(): checks@unserialize()return forfalse(with special handling for the legitimateb:0;case) and returns the original value unchanged rather than re-serialisingfalse.inc/compat/class-elementor-compat.phpregenerate_css()gains a DB-level fallback. Duplication runs in the network admin context where\Elementor\Pluginis not loaded. Previouslyregenerate_css()returned early with no effect. The fallback deletes_elementor_csspostmeta and the_elementor_global_cssoption so Elementor regenerates its compiled CSS on the first front-end visit.Tests
22 tests / 85 assertions, all passing.
New:
test_update_preserves_elementor_kit_byte_count— the regression test for this exact report. Inserts a realistic Kit payload (~6,387 bytes) intowp_postmeta, runs all 7 URL replacement passes throughMUCD_Data::update(), then asserts:#6EC1E4,#54595F,#7A7A7A,#61CE70) survive intactcustom_cssbrand colours survive; hero background URL is rewrittenAdditional unit tests:
test_get_primary_key_returns_correct_column,test_get_primary_key_is_cached,test_try_replace_elementor_kit_page_settings,test_try_replace_multiple_passes_preserve_data,test_try_replace_returns_original_on_unserialize_failure,test_try_replace_serialized_false_not_treated_as_error.Files
inc/duplication/data.php— primary key lookup + update fix + unserialize guardinc/compat/class-elementor-compat.php— DB fallback for CSS cache cleartests/WP_Ultimo/Duplication/MUCD_Data_Test.php— 8 new testsVerification
vendor/bin/phpunit --no-coverage --filter MUCD_Data_Test # Expected: OK (22 tests, 85 assertions)Summary by CodeRabbit
Bug Fixes
Improvements
Tests